mod helper;
mod logger;
mod models;
mod pool;
mod secs;
mod sml;

pub type AppResult<T> = Result<T, AppError>;
use models::WindowState;
use quic_secs_driver::SecsError;
use std::{fmt::Debug, path::PathBuf, str::FromStr, sync::OnceLock};
use tauri::{ipc::Channel, App, Manager, Window, WindowEvent};
use tauri_plugin_dialog::DialogExt;
const VERSION: &'static str = env!("CARGO_PKG_VERSION");

pub enum AppError {
    Message(String),
}

impl serde::Serialize for AppError {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::ser::Serializer,
    {
        match self {
            AppError::Message(str) => serializer.serialize_str(str),
        }
    }
}

impl Debug for AppError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Message(arg0) => f.debug_tuple("Message").field(arg0).finish(),
        }
    }
}

impl From<SecsError> for AppError {
    fn from(value: SecsError) -> Self {
        AppError::Message(value.to_string())
    }
}

#[cfg(debug_assertions)]
fn get_root() -> PathBuf {
    std::env::current_dir()
        .unwrap()
        .parent()
        .unwrap()
        .to_path_buf()
}

#[cfg(not(debug_assertions))]
fn get_root() -> PathBuf {
    std::env::current_exe()
        .unwrap()
        .parent()
        .unwrap()
        .to_path_buf()
}

#[derive(serde::Serialize)]
pub(crate) struct AppInfo {
    version: String,
}

static mut CHANNEL: OnceLock<Channel<crate::secs::ConnectionEvent>> = OnceLock::new();

fn channel_send(evt: crate::secs::ConnectionEvent) -> AppResult<()> {
    unsafe {
        let ref_channel = &raw mut CHANNEL;
        match (*ref_channel).get() { Some(channel) => {
            channel
                .send(evt)
                .or_else(|err| Err(AppError::Message(err.to_string())))
        } _ => {
            Err(AppError::Message("channel is not set".to_string()))
        }}
    }
}

#[tauri::command]
async fn init(channel: Channel<crate::secs::ConnectionEvent>) -> AppResult<AppInfo> {
    unsafe {
        let ref_channel = &raw mut CHANNEL;
        if let Some(_) = (*ref_channel).take() {
            block_on(pool::clear());
        }
        (*ref_channel)
            .set(channel)
            .or_else(|_| Err(AppError::Message("error on init".to_string())))?;
        Ok(AppInfo {
            version: VERSION.to_string(),
        })
    }
}

#[tauri::command]
fn strip_path(path: String) -> AppResult<String> {
    let path_buf = PathBuf::from(path);
    let dir = get_root();
    println!("app root = {:?}", dir);
    let result = path_buf
        .strip_prefix(dir)
        .or(Err(AppError::Message("strip failed".to_string())))?;
    let str = result
        .to_str()
        .ok_or(AppError::Message("strip failed".to_string()))?;
    Ok(str.to_string())
}

#[tauri::command]
fn fill_path(path: String) -> AppResult<String> {
    let mut result = PathBuf::from_str(&path).or(Err(AppError::Message(format!(
        "log dir {} is invalid path",
        path
    ))))?;
    fill_path_internal(&mut result);
    let str = result.to_str().ok_or(AppError::Message("()".to_string()))?;
    Ok(str.to_string())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_clipboard_manager::init())
        .plugin(tauri_plugin_opener::init())
        .plugin(tauri_plugin_fs::init())
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_shell::init())
        // .append_invoke_initialization_script(INITIALIZE_SCRIPT)
        .setup(|app| {
            app_setup(app);
            Ok(())
        })
        .on_window_event(app_window_event)
        .invoke_handler(tauri::generate_handler![
            init,
            strip_path,
            fill_path,
            get_unique_id,
            sml::sml_load,
            sml::sml_save,
            sml::sml_sync_transaction,
            sml::sml_del_transaction,
            sml::sml_swap_transaction,
            sml::sml_get_preference,
            sml::sml_set_preference,
            sml::sml_get_configuration,
            sml::sml_set_configuration,
            sml::sml_open_log_dir,
            pool::create_connection,
            pool::remove_connection,
            secs::secs_connect,
            secs::secs_shutdown,
            secs::secs_send_primary,
            secs::secs_copy_message_text,
            secs::secs_copy_value_text,
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

// static INITIALIZE_SCRIPT: &'static str = r#"
//     let winState = window.localStorage.getItem("win.state");
//     if (winState != null && winState != "") {
//         setTimeout(()=>{}
//         const tWindow = window.__TAURI__.window.getCurrentWindow();
//         let winStateObj = JSON.parse(winState);
//         if (winStateObj.isMaximized) {
//             tWindow.maximize();
//         }
//         else {
//             tWindow.setSize({width:winStateObj.width,height:winStateObj.height});
//             tWindow.setPosition({width:winStateObj.x,height:winStateObj.y});
//         },300)
//     }
//     "#;

fn app_setup(app: &mut App) {
    // let webview_win = app.get_webview_window("main").unwrap();
    // // webview_win.pos
    // _ = webview_win.eval("setTimeout(()=>{window.initlizeApp();},600)");
    if let Some(win) = app.get_webview_window("main") {
        WindowState::restore(&win);
    }
}

fn app_window_event(window: &Window, event: &WindowEvent) {
    match event {
        WindowEvent::CloseRequested { api, .. } => {
            let dialog_result = window
                .app_handle()
                .dialog()
                .message("Are you sure to close simulator?")
                .title("confirm")
                .buttons(tauri_plugin_dialog::MessageDialogButtons::OkCancel)
                .blocking_show();
            if !dialog_result {
                api.prevent_close();
            }
            WindowState::save(window);
        }
        _ => {}
    }
}
#[inline]
pub fn block_on<F: std::future::Future>(future: F) -> F::Output {
    // tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(future))
    tokio::task::block_in_place(|| tauri::async_runtime::block_on(future))
}

pub(crate) fn fill_path_internal(path: &mut PathBuf) {
    if path.is_relative() {
        let old_path = path.clone();
        path.clear();
        path.push(get_root().join(old_path));
    }
}

#[tauri::command]
fn get_unique_id() -> String {
    nanoid::nanoid!(10)
}
